package com.google.code.p.restrpc.core.url;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.google.code.p.restrpc.DefaultServiceNode;
import com.google.code.p.restrpc.RestfulRequest;
import com.google.code.p.restrpc.RpcProtocal;
import com.google.code.p.restrpc.RpcRequest;
import com.google.code.p.restrpc.ServiceNode;
import com.google.code.p.restrpc.UrlParser;

/**
 * @author dz
 */
public class DefaultUrlParser implements UrlParser {
    
    /*
     * (non-Javadoc)
     */
    public RpcRequest[] parse(String url) {
        if (StringUtils.isEmpty(url)) {
            throw new IllegalArgumentException("url can't be null or empty!");
        }
        
        if (url.startsWith(RpcProtocal.HTTP.prefix())) {
            return decomposeUrl(url,
                                RpcProtocal.HTTP.prefix(),
                                RpcRequestProvider.restfulRequestProvider);
        }
        else if (url.startsWith(RpcProtocal.HTTPS.prefix())) {
            return decomposeUrl(url,
                                RpcProtocal.HTTPS.prefix(),
                                RpcRequestProvider.secureRestfulRpcRequestProvider);
        }
        else {
            throw new IllegalArgumentException("Unsupported protocal for " + url);
        }
    }
    
    /**
     * @param url
     * @param protocalPrefix
     * @param rpcRequestProvider
     * @return
     */
    protected RpcRequest[] decomposeUrl(String url,
                                        String protocalPrefix,
                                        RpcRequestProvider rpcRequestProvider) {
        List<ServiceNode> serviceNodes = new ArrayList<ServiceNode>();
        DefaultServiceNode serviceNode = new DefaultServiceNode();
        serviceNodes.add(serviceNode);
        
        String temp = url.substring(protocalPrefix.length());
        char[] chars = temp.toCharArray();
        
        boolean colonFound = false, commaFound = false, slashFound = false, poundFound = false;
        int urlPart = 0; // {0:address,1:port,2:context,3:service,4:method}
        
        StringBuilder addressSb = new StringBuilder();
        StringBuilder portSb = new StringBuilder();
        StringBuilder contextSb = new StringBuilder();
        StringBuilder serviceSb = new StringBuilder();
        StringBuilder methodSb = new StringBuilder();
        
        for (int i = 0, length = chars.length; i < length; i++) {
            char c = chars[i];
            switch (c) {
                case ':':
                    if (urlPart < 1) {
                        urlPart = 1;
                    }
                    colonFound = true;
                    break;
                case '#':
                    if (poundFound) {
                        throw new IllegalArgumentException("Duplicate pound(#) found!");
                    }
                    poundFound = true;
                    urlPart = 4;
                    break;
                case ',':
                    if (urlPart < 2) {
                        serviceNode.setAddress(addressSb.toString());
                        try {
                            if (!StringUtils.isEmpty(portSb.toString())) {
                                serviceNode.setPort(Integer.parseInt(portSb.toString()));
                            }
                        }
                        catch (NumberFormatException e) {
                            throw new InvalidPortFormatException(portSb.toString(),
                                                                 e);
                        }
                        
                        serviceNode = new DefaultServiceNode();
                        serviceNodes.add(serviceNode);
                        
                        addressSb = new StringBuilder();
                        portSb = new StringBuilder();
                        
                        urlPart = 0;
                    }
                    commaFound = true;
                    break;
                case '/':
                    if (slashFound) {
                        urlPart = 3;
                    }
                    else {
                        serviceNode.setAddress(addressSb.toString());
                        try {
                            if (!StringUtils.isEmpty(portSb.toString())) {
                                serviceNode.setPort(Integer.parseInt(portSb.toString()));
                            }
                        }
                        catch (NumberFormatException e) {
                            throw new InvalidPortFormatException(portSb.toString(),
                                                                 e);
                        }
                        
                        slashFound = true;
                        urlPart = 2;
                    }
                default:
                    switch (urlPart) {
                        case 0:
                            addressSb.append(c);
                            break;
                        case 1:
                            portSb.append(c);
                            break;
                        case 2:
                            contextSb.append(c);
                            break;
                        case 3:
                            serviceSb.append(c);
                            break;
                        case 4:
                            methodSb.append(c);
                            break;
                    }
            }
        }
        
        String context = contextSb.toString();
        String serviceName = serviceSb.toString();
        String methodName = methodSb.toString();
        
        List<RpcRequest> result = new ArrayList<RpcRequest>();
        for (int i = 0, length = serviceNodes.size(); i < length; i++) {
            ServiceNode node = serviceNodes.get(i);
            AbstractRpcRequest request = rpcRequestProvider.getInstance();
            
            request.address = node.getAddress();
            request.port = node.getPort();
            request.context = context;
            request.serviceName = serviceName;
            request.methodName = methodName;
            
            if (length == 1) {
                request.url = url;
            }
            else {
                StringBuilder urlSb = new StringBuilder(request.getProtocal()
                                                             .prefix());
                urlSb.append(request.getAddress());
                if (request.getPort() != 80) {
                    urlSb.append(":").append(request.getPort());
                }
                urlSb.append(request.getContext())
                     .append(request.getServiceName());
                if (!StringUtils.isEmpty(request.getMethodName())) {
                    urlSb.append("#").append(request.getMethodName());
                }
                request.url = urlSb.toString();
            }
            result.add(request);
        }
        
        return result.toArray(RpcRequest.EMPTY_ARRAY);
    }
    
    private static interface RpcRequestProvider {
        
        public static RpcRequestProvider restfulRequestProvider = new RpcRequestProvider() {
            
            public DefaultRestfulRequest getInstance() {
                return new DefaultRestfulRequest();
            }
            
        };
        
        public static RpcRequestProvider secureRestfulRpcRequestProvider = new RpcRequestProvider() {
            
            public DefaultSecureRestfulRequest getInstance() {
                return new DefaultSecureRestfulRequest();
            }
            
        };
        
        public AbstractRpcRequest getInstance();
    }
    
    public static void main(String[] args) {
        RpcRequest[] requests = new DefaultUrlParser().parse("https://192.168.0.110:1234,192.168.0.211:80,192.168.0.212/restrpc/helloService#sayHello");
        for (RpcRequest rpcRequest : requests) {
            System.out.println(rpcRequest.getUrl());
            System.out.println(rpcRequest.getProtocal());
            System.out.println(rpcRequest.getContext());
            System.out.println(rpcRequest.getServiceName());
            System.out.println(rpcRequest.getMethodName());
            System.out.println(rpcRequest.getAddress());
            System.out.println(rpcRequest.getPort());
            System.out.println("-----------------------------------------");
        }
        
        requests = new DefaultUrlParser().parse("http://192.168.0.110:1234,192.168.0.211:80,192.168.0.212/restrpc/helloService#sayHello");
        for (RpcRequest rpcRequest : requests) {
            System.out.println(rpcRequest.getUrl());
            System.out.println(rpcRequest.getProtocal());
            System.out.println(rpcRequest.getContext());
            System.out.println(rpcRequest.getServiceName());
            System.out.println(rpcRequest.getMethodName());
            System.out.println(rpcRequest.getAddress());
            System.out.println(rpcRequest.getPort());
            System.out.println("-----------------------------------------");
        }
        
    }
    
}
